package com.scheduled.manager;

import com.scheduled.manager.constant.ScheduledConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;


/**
 * 延时任务管理器
 *
 * @author : z_sz
 * @date : 2020-08-08 10:45
 **/
@Slf4j
@Component
@EnableScheduling
public class ScheduledManager {

    /**
     * spring线程池任务调度类
     */
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    /**
     * 系统维护线程安全ConcurrentHashMap,用来存储ScheduledFuture对象
     */
    private final Map<String,ScheduledFuture> scheduledMap = new ConcurrentHashMap<>();

    /**
     * 设置延迟任务
     *
     * @param runnable      Runnable实现类，run()方法中为要延迟执行的任务体
     * @param localDateTime 任务执行的时间点，因为项目中必须要用jdk8的LocalDateTime，不让用Date,所以传参是基于LocalDateTime的
     * @param key           任务主键，任务唯一标识
     * @return 设置结果
     */
    public Boolean delayExecute(Runnable runnable, LocalDateTime localDateTime,String key) {
        if(Objects.isNull(localDateTime) || StringUtils.isBlank(key)) {
            log.error("延迟任务传递参数错误，localDateTime:{},任务主键:{}",localDateTime,key);
            return false;
        }
        // 任务延迟执行的时间毫秒值
        long delayTimeMilli = localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        // 当前时间毫秒值
        LocalDateTime nowTime = LocalDateTime.now();
        long nowTimeMilli = nowTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        if (delayTimeMilli < nowTimeMilli) {
            log.error("延迟任务执行时间{}小于当前时间{}", localDateTime, nowTime);
            return false;
        }
        // LocalDateTime转成Date
        Date date = Date.from(
                localDateTime.atZone(
                        ZoneId.systemDefault()
                ).toInstant()
        );
        // 获取当前等待队列的长度
        int size = threadPoolTaskScheduler.getScheduledThreadPoolExecutor().getQueue().size();
        // 当等待队列超过最大限制值时，拒绝处理新的定时任务请求，等队列长度降到最大限制值以下时，新线程会再次进入队列
        if(size >= ScheduledConstant.QUEUE_MAX_LENGTH) {
            // 实际开发中，这里可以抛异常，进行全局处理，返回自定义异常处理结果
            log.info("线程池等待队列最大长度超过最大限制值");
            return false;
        }
        // 开启线程定时，延迟执行
        ScheduledFuture<?> future = threadPoolTaskScheduler.schedule(runnable, date);
        scheduledMap.put(key,future);
        log.info("定时任务已开启，将在{}执行",localDateTime);
        return true;
    }

    /**
     * 根据任务唯一标识取消任务（在任务还未执行的时候）
     * @param id 任务主键、唯一标识
     * @return 取消结果
     */
    public boolean cancelScheduled(String id) {
        if(StringUtils.isBlank(id)) {
            log.error("参数传递错误,id：{}",id);
            return false;
        }
        ScheduledFuture future = scheduledMap.get(id);
        // 取消延迟任务,传参为true,表示无论任务有没有在执行，都会取消
        boolean cancelResult = future.cancel(true);
        if(!cancelResult) {
            log.info("延迟任务取消不成功,任务唯一标识:{}",id);
            return false;
        }
        scheduledMap.remove(id);
        return true;
    }

}
